#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/wait.h>
//测试sig.cc通过命令行参数执行2号信号杀死这个进程
using namespace std;
void test1()
{
    while(1)
    {
        cout<<"hello,i am child,pid:"<<getpid()<<endl;
        sleep(1);
    }
}

void myhander(int signo)//会把信号序号传参给signo参数
{
    cout<<"i get a signo:"<<signo<<endl;
}
//signal函数：signal（int signum,sighandler_t handler）
//第一个参数是信号的序号第二个参数是要自定义的行为函数，必须是无返回值的只有一个整形参数的函数，因为：typdef void (*sighandler_t)(int)

//信号的处理方式只有三种：1，默认处理 2，忽略  3，自定义动作


//测试signal函数，修改信号对应的行为：
void test2()
{
    signal(2,myhander);//2号信号默认行为是杀死进程，如果我们自定义了行为，那么收到信号就会执行我们自定义的行为，就不会杀死进程了
    test1();
}

//测试，有些信号是不会被我们改写的，例如9（杀死进程kill）,19（stop暂停进程）号信号,这是操作系统为了防止我们随意修改信号导致无法终止我们的进程的情况出现
void test3()
{
    signal(9,myhander);//这是出于安全和稳定性的考虑，因为SIGKILL用于确保系统管理员和某些关键进程能够强制终止任何进程，即使该进程处于无法响应其他信号的状态。
    test1();
}

//测试系统调用raise函数,raise(int signum)，会给调用这个函数的进程自己发送一个对应的信号
void test4()
{
    int i=0;
    signal(2,myhander);
    while(true)
    {
        i++;
        cout<<"hello"<<i<<endl;
        sleep(1);
        if(i==5)
        {
            raise(2);
        }
        if(i==8)
        {
            raise(9);
        }
    }
}

//测试abort函数，会给自己这个进程发送一个SIGABRT信号，如果没有自定义对信号的捕获就会终止进程
void test5()
{
    int i=0;
    while(true)
    {
        i++;
        if(i==5)
        {
            abort();
        }
    }
}

//测试代码段出错的时候本质是发送信号给进程处理的，例如：当除数为0的时候，会发送8号信号SIGFPE
void test6()
{
    signal(SIGFPE,myhander);//然而我们会发现进程会一直收到8号信号，进入死循环
    //解释：myhander没有正确地终止程序或者没有清除导致异常的条件，那么程序可能会继续执行并再次尝试除以零的操作，从而再次触发SIGFPE信号。这可能导致信号处理函数被反复调用，形成死循环。
    //所以，我们自己设定的自定义行为中对一些必须要处理的错误要进行合理的处理，比如退出进程，或者先记录一下错误日志再退出
    int a=1;
    int b=0;
    int c=a/b;
}

//对于信号的处理方式除了自定义外也有忽略和默认处理，我们可以使用SIG_DFL,SIG_IGN来设定
void test()
{
    //设置为自定义行为
    signal(2,myhander);

    //忽略：收到2号信号后不做任何处理
    signal(2,SIG_IGN);

    //恢复默认处理：2号进程的默认处理就是直接终止进程
    signal(2,SIG_DFL);
}

//补充：核心转储：Core dump（核心转储）是操作系统在进程异常终止时（通常是因为收到某些信号，如SIGSEGV、SIGILL、SIGFPE、SIGABRT等）将进程的内存映像转储到文件中的行为。这个文件通常被称为core文件或core dump文件。
//为什么要核心转储：核心转储文件包含了程序终止时的内存状态，包括程序计数器、寄存器内容、内存页和堆栈信息等。开发者可以使用调试器（如gdb）来分析这个文件，以确定程序崩溃的原因和发生崩溃时程序的状态。
//核心转储的不足：核心转储文件可能非常大，特别是对于内存使用量大的程序。这可能会迅速消耗磁盘空间。
//可以使用ulimit -c 0来禁用核心转储，或者ulimit -c 字节数 来启用核心转储并分配一定额度空间给其使用
void mywait(int signo)
{
    int status=0;
    cout<<"i get a signal :"<<signo<<endl;
    pid_t rid=waitpid(-1,&status,0);
    if(rid>0)
    {
        cout<<"child qiut info:"<<"rid:"<<rid<<"exit code:"<<((status>>8)&0xFF)<<"exit signal:"<<(status&0x7F)<<"core dump:"<<((status>>7)&1)<<endl;
        //对于输出型参数status，它记录了子进程的退出码（中间两个字节），回收子进程使用的信号（低位的七个bit位），是否被核心转储core dump（低位的第七个bit位）
        //自然结束的时候信号是0，表示并没有使用信号杀死这个子进程，当我们发送信号杀死子进程后，退出信号就会被记录
    }
}

//测试通过信号进程等待：子进程退出的时候会给父进程发送一个SIGCHLD信号，我们可以使用这个信号从而改变对子进程的阻塞的方式的等待
void test7()
{
    signal(SIGCHLD,mywait);//子进程一退出就会触发这个行为，从而对子进行等待，通过信号来控制等待，父进程就不需要一直阻塞等待子进程
    pid_t id=fork();
    if(id==0)
    {
        int ct=10;
        while(1)
        {
            cout<<"i am a child:"<<getpid()<<endl;
            ct--;
            sleep(1);
            if(ct==0)
            {
                break;
            }
        }
    }
    else 
    {
        //父进程执行自己的任务即可：
        int ct=100;
        while(ct)
        {
            ct--;
            sleep(1);
        }
    }
    

}

//特别注意：对于处理子进程的SIGCHLD信号的时候，随着unix的发展，在linux平台上，对这个信号进行了特殊化处理，如果我们显示的使用signal函数将SIGCHLD的行为设定为SIG_IGN忽略的话，并不是不做处理，而是直接帮我们自动等待了
//测试：SIG_CHLD信号显示忽略之后系统会自动帮我们等待子进程：
void test8()
{
    signal(SIGCHLD,SIG_IGN);//我们显示的设置自定义行为为忽略
    //注意：一定要显示的定义为忽略，因为默认也是忽略但是不会做任何处理，显示的忽略就会帮我们等待，这个信号的显示忽略是特殊处理过的
    pid_t id=fork();
    if(id==0)
    {
        int ct=10;
        while(1)
        {
            cout<<"i am a child:"<<getpid()<<endl;
            ct--;
            sleep(1);
            if(ct==0)
            {
                break;
            }
        }
    }
    else 
    {
        //父进程执行自己的任务即可：
        int ct=100;
        while(ct)
        {
            ct--;
            sleep(1);
        }
    }
}
int main()
{
    test8();
    return 0;
}
